import '../node_modules/skulpt/dist/skulpt.min.js'
import '../node_modules/skulpt/dist/skulpt-stdlib.js'
import Blockly from '../node_modules/blockly/core'
import 'blockly/python_compressed'
import 'blockly/blockly_compressed'
import 'blockly/blocks_compressed'

Sk.configure({
  __future__: Sk.python3
})

function PythonToBlock () {}

// 常量
PythonToBlock.prototype.FLAG = 'BLOCKTOPYTHON' // block转python的标志

/**
 * init函数，整个项目最关键的函数
 * 此函数生成了一个blockly工作区primaryWorkspace
 * 在这个工作区上添加了一个changeListener，实现了由blockly向python的转化
 * 然后在init函数里添加了实现python向blockly转化的逻辑函数
 * 为了能够实时转化添加了计时器
 */
// eslint-disable-next-line no-unused-vars
window.init = function () {
  // 生成主要工作区
  const primaryWorkspace = Blockly.inject('primaryDiv', {
    media: 'https://unpkg.com/blockly/media/',
    toolbox: document.getElementById('toolbox'),
    trashcan: true,
    zoom: {
      controls: true,
      wheel: true,
      startScale: 1.0,
      maxScale: 3,
      minScale: 0.3,
      scaleSpeed: 1.2,
      pinch: true
    }
  })

  primaryWorkspace.registerToolboxCategoryCallback(
    'Class',
    PythonToBlock.prototype.defineFlyoutCallback
  )

  // lastRightXml 记录最近一次符合格式的xml对象
  let lastRightXml = null
  const filename = 'user_code.py'
  // oldword 记录改变之前的python代码
  let oldword = document.getElementById('showCode').value
  // time 定时器执行的程序
  const time = function () {
    const keyword = document.getElementById('showCode').value
    if (keyword !== oldword) {
      // pyhonSource 从文本框中获取的python代码（一段字符串）
      const pythonSource = keyword
      // xmlBlock 根据pythonSource生成的xml对象的字符串
      const xmlBlock = PythonToBlock.prototype.convertSource(
        filename,
        pythonSource
      ).xml
      // xmlBlockPredict 将xmlBlock转化为xml对象
      try {
        const xmlBlockPredict =
          xmlBlock === '' ? 'WrongMatch' : Blockly.Xml.textToDom(xmlBlock)
        if (xmlBlockPredict !== 'WrongMatch') {
          // 调用Blockly.Xml中的函数，此函数功能为：清除工作区中的blocks，根据lastRightXml重新加载blocks到工作区
          Blockly.Xml.clearWorkspaceAndLoadFromXml(
            xmlBlockPredict,
            primaryWorkspace
          )
          // 记录此次符合格式的xml对象
          lastRightXml = xmlBlockPredict
        }
      } catch (e) {
        console.log('解析失败！可能是组件拼插有问题，或者未找到组件定义。' + e)
        Blockly.Xml.clearWorkspaceAndLoadFromXml(
          lastRightXml,
          primaryWorkspace
        )
      }
      oldword = keyword
    }
  }

  // 下面的逻辑旨在实现实时更新
  let interVal = null
  // 根据焦点所在位置将FLAG修改
  setInterval(function () {
    if (document.activeElement.tagName === 'svg') {
      PythonToBlock.prototype.FLAG = 'BLOCKTOPYTHON'
    } else {
      if (document.activeElement.tagName === 'TEXTAREA') {
        PythonToBlock.prototype.FLAG = 'PYTHONTOBLOCK'
      }
    }
    if (PythonToBlock.prototype.FLAG === 'PYTHONTOBLOCK') {
      interVal = setTimeout(time, 0)
    } else {
      clearTimeout(interVal)
    }
  }, 100)

  // 实现了blockly到python的转换
  primaryWorkspace.addChangeListener(function (event) {
    if (primaryWorkspace.id === event.workspaceId) {
      if (PythonToBlock.prototype.FLAG === 'BLOCKTOPYTHON') {
        let code = Blockly.Python.workspaceToCode(primaryWorkspace)
        const variablesDef = /^([a-zA-Z0-9_]+ = None[\n])*([ ]*[\n]+)+$/gm
        code = code.replace(variablesDef, '')
        const firstSpace = /^[\0\n\r'']+$/
        code = code.replace(firstSpace, '')
        document.getElementById('showCode').value = code
        document.getElementsByTagName('TEXTAREA').value = code
        console.log(document.getElementsByTagName('TEXTAREA').value)
      }
    }
  })
}

// 海象表达式，处理字符串用，目前仍有bug
// eslint-disable-next-line no-unused-vars
const sourceProcess = function (keyword) {
  const walrusRegex = /^[a-zA-Z0-9_]*[ ]*:=[ ]*[a-zA-Z0-9_]*$/gm
  const walrusExpressions = walrusRegex.exec(keyword)
  if (walrusExpressions !== null) {
    const argsRegex = /^[a-zA-Z0-9_]+$/g
    let processSource = keyword.replace(walrusRegex, 'walrus()')
    processSource = processSource.replace('walrus()', 'walrus(' + 0 + ')')
    let count = 0
    while (
      processSource.search('walrus(' + 0 + ')') &&
      count < walrusExpressions.length
    ) {
      const argsOfWalrus = walrusExpressions[count].split(' ')
      let args1 = null
      let args2 = null
      for (i in argsOfWalrus) {
        if (i.search(argsRegex) && args1 === null) {
          args1 = i
        } else {
          if (i.search(argsRegex) && args2 === null) {
            args2 = i
          }
        }
      }
      processSource = processSource.replace(
        'walrus(' + 0 + ')',
        'walrus(' + args1 + ', ' + args2 + ')'
      )
      count = count + 1
      processSource = processSource.replace('walrus()', 'walrus(' + 0 + ')')
    }
    return processSource
  } else {
    const processSource = keyword
    return processSource
  }
}

/**
 * 生成xml对象的字符串
 * @param {*} xml 由convertSourceToCodeBlock函数返回组件的xml对象
 * @returns xml对象的字符串
 */
PythonToBlock.xmlToString = function (xml) {
  return new XMLSerializer().serializeToString(xml)
}

PythonToBlock.prototype.convertSourceToCodeBlock = function (pythonSource) {
  const xml = document.createElement('xml')
  xml.appendChild(PythonToBlock.rawBlock(pythonSource))
  return PythonToBlock.xmlToString(xml)
}

PythonToBlock.prototype.convertSource = function (filename, pythonSource) {
  const xml = document.createElement('xml')
  // Attempt parsing - might fail!
  let parse
  let ast = null
  //  symbol_table;
  let error
  let badChunks = []
  const originalSource = pythonSource
  this.source = pythonSource.split('\n')
  let previousLine = 1 + this.source.length
  if (pythonSource.trim() === '') {
    console.log(pythonSource.trim())
    if (badChunks.length) {
      xml.appendChild(PythonToBlock.rawBlock(badChunks.join('\n')))
    }
    return { xml: PythonToBlock.xmlToString(xml), error: null, rawXml: xml }
  }
  try {
    parse = Sk.parse(filename, pythonSource)
    ast = Sk.astFromParse(parse.cst, filename, parse.flags)
  } catch (e) {
    error = e
    if (
      e.traceback &&
      e.traceback.length &&
      e.traceback[0].lineno &&
      e.traceback[0].lineno < previousLine
    ) {
      previousLine = e.traceback[0].lineno - 1
      badChunks = badChunks.concat(this.source.slice(previousLine))
      this.source = this.source.slice(0, previousLine)
      pythonSource = this.source.join('\n')
    } else {
      xml.appendChild(PythonToBlock.rawBlock(originalSource))
      return { xml: PythonToBlock.xmlToString(xml), error, rawXml: xml }
    }
  }
  this.comments = {}
  if (parse !== undefined) {
    for (const commentLocation in parse.comments) {
      const lineColumn = commentLocation.split(',')
      const yLocation = parseInt(lineColumn[0], 10)
      this.comments[yLocation] = parse.comments[commentLocation]
    }
  }
  this.highestLineSeen = 0
  this.levelIndex = 0
  this.nextExpectedLine = 0
  this.measureNode(ast)
  try {
    const converted = this.convert(ast)
    if (converted !== null) {
      for (let block = 0; block < converted.length; block += 1) {
        if (converted[block].constructor !== Array) {
          xml.appendChild(converted[block])
        } else {
          xml.appendChild(converted[block][0])
        }
      }
    }
    if (badChunks.length) {
      xml.appendChild(PythonToBlock.rawBlock(badChunks.join('\n')))
    }
    return {
      xml: PythonToBlock.xmlToString(xml),
      error: null,
      lineMap: this.lineMap,
      comments: this.comments,
      rawXml: xml
    }
  } catch (e) {
    console.log('解析失败！可能是不存在与node同名的块返回。' + e)
    xml.appendChild(PythonToBlock.rawBlock(originalSource))
    return { xml: PythonToBlock.xmlToString(xml), error, rawXml: xml }
  }
}

PythonToBlock.prototype.recursiveMeasure = function (node, nextBlockLine) {
  // console.log(Sk.astDump(node));
  if (node === undefined) {
    return
  }
  let myNext = nextBlockLine
  if ('orelse' in node && node.orelse.length > 0) {
    if (node.orelse.length === 1 && node.orelse[0]._astname === 'If') {
      myNext = node.orelse[0].lineno - 1
    } else {
      myNext = node.orelse[0].lineno - 1 - 1
    }
  }
  this.heights.push(nextBlockLine)
  if ('body' in node) {
    for (let i = 0; i < node.body.length; i++) {
      let next
      if (i + 1 === node.body.length) {
        next = myNext
      } else {
        next = node.body[i + 1].lineno - 1
      }
      this.recursiveMeasure(node.body[i], next)
    }
  }
  if ('orelse' in node) {
    for (let i = 0; i < node.orelse.length; i++) {
      let next
      if (i === node.orelse.length) {
        next = nextBlockLine
      } else {
        next = 1 + (node.orelse[i].lineno - 1)
      }
      this.recursiveMeasure(node.orelse[i], next)
    }
  }
}

PythonToBlock.prototype.measureNode = function (node) {
  this.heights = []
  this.recursiveMeasure(node, this.source.length - 1)
  this.heights.shift()
}

PythonToBlock.prototype.getSourceCode = function (frm, to) {
  const lines = this.source.slice(frm - 1, to)
  // Strip out any starting indentation.
  if (lines.length > 0) {
    const indentation = lines[0].search(/\S/)
    for (let i = 0; i < lines.length; i++) {
      lines[i] = lines[i].substring(indentation)
    }
  }
  return lines.join('\n')
}

PythonToBlock.prototype.convertBody = function (node, parent) {
  this.levelIndex += 1
  const isTopLevel = this.isTopLevel(parent)

  // Final result list
  const children = [] // The complete set of peers
  let root = null // The top of the current peer
  let current = null // The bottom of the current peer

  function addPeer (peer) {
    if (root == null) {
      children.push(peer)
    } else {
      children.push(root)
    }
    root = peer
    current = peer
  }

  function finalizePeers () {
    if (root != null) {
      children.push(root)
    }
  }

  function nestChild (child) {
    if (root == null) {
      root = child
      current = child
    } else if (current == null) {
      root = current
    } else {
      const nextElement = document.createElement('next')
      nextElement.appendChild(child)
      current.appendChild(nextElement)
      current = child
    }
  }

  let lineNumberInBody = 0
  let lineNumberInProgram
  let previousLineInProgram = null
  let distance
  let skippedLine
  //  commentCount;
  //  previousHeight = null;
  let previousWasStatement = false
  let visitedFirstLine = false
  let wasFirstLine = false

  // Iterate through each node
  for (let i = 0; i < node.length; i++) {
    lineNumberInBody += 1

    lineNumberInProgram = node[i].lineno
    distance = 0
    wasFirstLine = true
    if (previousLineInProgram != null) {
      distance = lineNumberInProgram - previousLineInProgram - 1
      // eslint-disable-next-line no-unused-vars
      wasFirstLine = false
    }
    // eslint-disable-next-line no-unused-vars
    lineNumberInBody += distance

    // Handle earlier comments
    // commentCount = 0;
    for (const commentLineInProgram in this.comments) {
      if (commentLineInProgram < lineNumberInProgram) {
        const commentChild = this.ast_Comment(
          this.comments[commentLineInProgram],
          commentLineInProgram
        )
        if (previousLineInProgram == null) {
          nestChild(commentChild)
        } else {
          const skippedPreviousLine =
            Math.abs(previousLineInProgram - commentLineInProgram) > 1
          if (isTopLevel && skippedPreviousLine) {
            addPeer(commentChild)
          } else {
            nestChild(commentChild)
          }
        }
        previousLineInProgram = commentLineInProgram
        this.highestLineSeen = Math.max(
          this.highestLineSeen,
          parseInt(commentLineInProgram, 10)
        )
        distance = lineNumberInProgram - previousLineInProgram
        delete this.comments[commentLineInProgram]
        // commentCount += 1;
      }
    }

    distance = lineNumberInProgram - this.highestLineSeen
    this.highestLineSeen = Math.max(lineNumberInProgram, this.highestLineSeen)

    // Now convert the actual node
    const height = this.heights.shift()
    const originalSourceCode = this.getSourceCode(lineNumberInProgram, height)
    const newChild = this.convertStatement(node[i], originalSourceCode, parent)

    // Skip null blocks (e.g., imports)
    if (newChild == null) {
      continue
    }

    skippedLine = distance > 1
    previousLineInProgram = lineNumberInProgram
    // previousHeight = height;

    // Handle top-level expression blocks
    if (isTopLevel && newChild.constructor === Array) {
      addPeer(newChild[0])
      // Handle skipped line
    } else if (isTopLevel && skippedLine && visitedFirstLine) {
      addPeer(newChild)
      // The previous line was not a Peer
    } else if (isTopLevel && !previousWasStatement) {
      addPeer(newChild)
      // Otherwise, always embed it in there.
    } else {
      nestChild(newChild)
    }
    previousWasStatement = newChild.constructor !== Array

    visitedFirstLine = true
  }

  // Handle comments that are on the very last line
  const lastLineNumber = lineNumberInProgram + 1
  if (lastLineNumber in this.comments) {
    const commentChild = this.ast_Comment(
      this.comments[lastLineNumber],
      lastLineNumber
    )
    if (isTopLevel && !previousWasStatement) {
      addPeer(commentChild)
    } else {
      nestChild(commentChild)
    }
    delete this.comments[lastLineNumber]
  }

  // Handle any extra comments that stuck around
  if (isTopLevel) {
    for (const commentLineInProgram in this.comments) {
      const commentChild = this.ast_Comment(
        this.comments[commentLineInProgram],
        commentLineInProgram
      )
      distance = commentLineInProgram - previousLineInProgram
      if (previousLineInProgram == null) {
        addPeer(commentChild)
      } else if (distance > 1) {
        addPeer(commentChild)
      } else {
        nestChild(commentChild)
      }
      previousLineInProgram = commentLineInProgram
      delete this.comments[lastLineNumber]
    }
  }
  finalizePeers()
  this.levelIndex -= 1
  return children
}

PythonToBlock.prototype.TOP_LEVEL_NODES = [
  'Module',
  'Expression',
  'Interactive',
  'Suite'
]

PythonToBlock.prototype.isTopLevel = function (parent) {
  return !parent || this.TOP_LEVEL_NODES.indexOf(parent._astname) !== -1
}

PythonToBlock.prototype.convert = function (node, parent) {
  const functionName = node._astname
  if (this[functionName] === undefined) {
    throw new Error('Could not find function: ' + functionName)
  }
  node._parent = parent
  return this[functionName](node, parent)
}

PythonToBlock.prototype.convertElements = function (key, values, parent) {
  const output = {}
  for (let i = 0; i < values.length; i++) {
    output[key + i] = this.convert(values[i], parent)
  }
  return output
}

function arrayMax (array) {
  return array.reduce(function (a, b) {
    return Math.max(a, b)
  })
}

function arrayMin (array) {
  return array.reduce(function (a, b) {
    return Math.min(a, b)
  })
}

PythonToBlock.prototype.convertStatement = function (
  node,
  // full_source,
  parent
) {
  try {
    return this.convert(node, parent)
  } catch (e) {
    const heights = this.getChunkHeights(node)
    const extractedSource = this.getSourceCode(
      arrayMin(heights),
      arrayMax(heights)
    )
    console.error(e)
    return PythonToBlock.rawBlock(extractedSource)
  }
}

PythonToBlock.prototype.getChunkHeights = function (node) {
  let lineNumbers = []
  // eslint-disable-next-line no-prototype-builtins
  if (node.hasOwnProperty('lineno')) {
    lineNumbers.push(node.lineno)
  }
  // eslint-disable-next-line no-prototype-builtins
  if (node.hasOwnProperty('body')) {
    for (let i = 0; i < node.body.length; i += 1) {
      const subnode = node.body[i]
      lineNumbers = lineNumbers.concat(this.getChunkHeights(subnode))
    }
  }
  // eslint-disable-next-line no-prototype-builtins
  if (node.hasOwnProperty('orelse')) {
    for (let i = 0; i < node.orelse.length; i += 1) {
      const subnode = node.orelse[i]
      lineNumbers = lineNumbers.concat(this.getChunkHeights(subnode))
    }
  }
  return lineNumbers
}

PythonToBlock.createBlock = function (
  type,
  lineNumber, // node.lineno
  fields,
  values,
  settings, // {}
  mutations, // {}
  statements
) {
  const newBlock = document.createElement('block')
  // Settings
  newBlock.setAttribute('type', type)
  newBlock.setAttribute('line_number', lineNumber)
  for (const setting in settings) {
    const settingValue = settings[setting]
    newBlock.setAttribute(setting, settingValue)
  }
  // Mutations
  if (mutations !== undefined && Object.keys(mutations).length > 0) {
    const newMutation = document.createElement('mutation')
    for (const mutation in mutations) {
      const mutationValue = mutations[mutation]
      if (mutation.charAt(0) === '@') {
        newMutation.setAttribute(mutation.substr(1), mutationValue)
      } else if (mutationValue != null && mutationValue.constructor === Array) {
        for (let i = 0; i < mutationValue.length; i++) {
          const mutationNode = document.createElement(mutation)
          mutationNode.setAttribute('name', mutationValue[i])
          newMutation.appendChild(mutationNode)
        }
      } else {
        const mutationNode = document.createElement('arg')
        if (mutation.charAt(0) === '!') {
          mutationNode.setAttribute('name', '')
        } else {
          mutationNode.setAttribute('name', mutation)
        }
        if (mutationValue !== null) {
          mutationNode.appendChild(mutationValue)
        }
        newMutation.appendChild(mutationNode)
      }
    }
    newBlock.appendChild(newMutation)
  }
  // Fields
  for (const field in fields) {
    const fieldValue = fields[field]
    const newField = document.createElement('field')
    newField.setAttribute('name', field)
    newField.appendChild(document.createTextNode(fieldValue))
    newBlock.appendChild(newField)
  }
  // Values
  for (const value in values) {
    const valueValue = values[value]
    const newValue = document.createElement('value')
    if (valueValue !== null) {
      newValue.setAttribute('name', value)
      newValue.appendChild(valueValue)
      newBlock.appendChild(newValue)
    }
  }
  // Statements
  if (statements !== undefined && Object.keys(statements).length > 0) {
    for (const statement in statements) {
      const statementValue = statements[statement]
      if (statementValue == null) {
        continue
      } else {
        for (let i = 0; i < statementValue.length; i += 1) {
          // In most cases, you really shouldn't ever have more than
          //  one statement in this list. I'm not sure Blockly likes
          //  that.
          const newStatement = document.createElement('statement')
          newStatement.setAttribute('name', statement)
          newStatement.appendChild(statementValue[i])
          newBlock.appendChild(newStatement)
        }
      }
    }
  }
  return newBlock
}

PythonToBlock.rawBlock = function (txt) {
  // lineno as second parameter!
  return PythonToBlock.createBlock('ast_Raw', 0, { wrongCode: txt })
}

PythonToBlock.prototype.CallAttribute = function (
  func,
  args,
  keywords,
  starargs,
  kwargs
) {
  const name = this.identifier(func.attr)
  if (func.value._astname === 'Name') {
    const module = this.identifier(func.value.id)
    if (module === 'plt' && name === 'plot') {
      if (args.length === 1) {
        return [
          block(
            'plot_line',
            func.lineno,
            {},
            {
              y_values: this.convert(args[0])
            },
            { inline: 'false' }
          )
        ]
      } else if (args.length === 2) {
        return [
          block(
            'plot_lineXY',
            func.lineno,
            {},
            {
              x_values: this.convert(args[0]),
              y_values: this.convert(args[1])
            },
            { inline: 'false' }
          )
        ]
      } else {
        throw new Error('Incorrect number of arguments to plt.plot')
      }
    } else if (
      module in PythonToBlock.KNOWN_MODULES &&
      name in PythonToBlock.KNOWN_MODULES[module]
    ) {
      const definition = PythonToBlock.KNOWN_MODULES[module][name]
      let blockName = definition[0]
      let isExpression = true
      if (blockName.charAt(0) === '*') {
        blockName = blockName.slice(1)
        isExpression = false
      }
      const fields = {}
      for (let i = 0; i < args.length; i++) {
        const argument = definition[1 + i]
        if (typeof argument === 'string') {
          fields[argument] = this.strValue(args[i])
        } else {
          const argumentName = argument[0]
          const argumentMapper = argument[1]
          fields[argumentName] = argumentMapper(this.strValue(args[i]))
        }
      }
      for (let i = 1 + args.length; i < definition.length; i++) {
        const first = definition[i][0]
        const second = definition[i][1]
        fields[first] = second
      }
      if (isExpression) {
        return block(blockName, func.lineno, fields)
      } else {
        return [block(blockName, func.lineno, fields)]
      }
    }
  }
  if (this.KNOWN_FUNCTIONS.indexOf(name) > -1) {
    switch (name) {
      case 'append':
        if (args.length !== 1) {
          throw new Error('Incorrect number of arguments to .append')
        }
        // Return as statement
        return [
          block(
            'lists_append',
            func.lineno,
            {},
            {
              ITEM: this.convert(args[0]),
              LIST: this.convert(func.value)
            },
            {
              inline: 'true'
            }
          )
        ]
      case 'strip':
        return block(
          'text_trim',
          func.lineno,
          { MODE: 'BOTH' },
          { TEXT: this.convert(func.value) }
        )
      case 'lstrip':
        return block(
          'text_trim',
          func.lineno,
          { MODE: 'LEFT' },
          { TEXT: this.convert(func.value) }
        )
      case 'rstrip':
        return block(
          'text_trim',
          func.lineno,
          { MODE: 'RIGHT' },
          { TEXT: this.convert(func.value) }
        )
      default:
        throw new Error('Unknown function call!')
    }
  } else {
    console.log(func, args, keywords, starargs, kwargs)
    const callArguments = {}
    for (let i = 0; i < args.length; i++) {
      callArguments['ARGUMENT' + i] = this.convert(args[i])
    }
    return block('function_call', { NAME: name }, callArguments)
    // throw new Error("Unknown function call or not implemented");
  }
}

PythonToBlock.prototype.identifier = function (node) {
  return Sk.ffi.remapToJs(node) // Sk.astDump(node)
}

/* ----- Nodes ---- */
/* ！！！下面是一些顶层的元素，这些元素不用修改！！！ */
/*
 * NO LINE OR COLUMN NUMBERS
 * Module
 * body: asdl_seq
 */
PythonToBlock.prototype.Module = function (node) {
  return this.convertBody(node.body, true)
}

/*
 * NO LINE OR COLUMN NUMBERS
 * Interactive
 * body: asdl_seq
 */
PythonToBlock.prototype.Interactive = function (body) {
  return this.convertBody(node.body)
}
/*
 * NO LINE OR COLUMN NUMBERS
 * TODO
 * body: expr_ty
 */
PythonToBlock.prototype.Expression = function (body) {
  this.body = body
}

PythonToBlock.prototype.convertElements = function (key, values, parent) {
  const output = {}
  for (let i = 0; i < values.length; i++) {
    output[key + i] = this.convert(values[i], parent)
  }
  return output
}

/*
 * NO LINE OR COLUMN NUMBERS
 *
 * body: asdl_seq
 */
PythonToBlock.prototype.Suite = function (body) {
  this.asdl_seq(node.body)
}
/* ！！！上面是非常关键的顶层元素，不要修改！！！ */

/* !!!以下是声明变量用的函数 */
/*
 * 声明变量
 * targets: asdl_seq
 * value: expr_ty
 */
PythonToBlock.prototype.Assign = function (node) {
  const targets = node.targets
  const value = node.value
  if (targets.length === 0) {
    throw new Error('Nothing to assign to!')
  } else if (targets.length === 1) {
    if (targets[0]._astname === 'Attribute') {
      return PythonToBlock.createBlock(
        'variables_set',
        node.lineno,
        {
          VAR:
            Sk.ffi.remapToJs(targets[0].value.id) +
            '.' +
            Sk.ffi.remapToJs(targets[0].attr) // targets
        },
        {
          VALUE: this.convert(value)
        }
      )
    } else {
      return PythonToBlock.createBlock(
        'variables_set',
        node.lineno,
        {
          VAR: PythonToBlock.prototype.nameStr(targets[0]) // targets
        },
        {
          VALUE: this.convert(value)
        }
      )
    }
  } else {
    return PythonToBlock.createBlock(
      'variables_set',
      node.lineno,
      {
        VAR: PythonToBlock.prototype.nameStr(targets[0]) // targets
      },
      {
        VALUE: this.multiAssign(targets[1], 1, node)
      }
    )
  }
}

PythonToBlock.prototype.multiAssign = function (left, leftPos, parent) {
  if (leftPos === parent.targets.length - 1) {
    return PythonToBlock.createBlock(
      'multi_assign',
      1,
      {},
      {
        leftValue: this.convert(left, parent),
        rightValue: this.convert(parent.value)
      }
    )
  }
  const leftNode = parent.targets[leftPos + 1]
  return PythonToBlock.createBlock(
    'multi_assign',
    1,
    {},
    {
      leftValue: this.convert(left, parent),
      rightValue: this.multiAssign(leftNode, leftPos + 1, parent)
    }
  )
}

/*
 * 声明数字变量
 * n: object
 */
PythonToBlock.prototype.Num = function (node) {
  const n = node.n
  return PythonToBlock.createBlock('math_number', node.lineno, {
    NUM: Sk.ffi.remapToJs(n)
  })
}

/*
 * 声明字符串变量
 * s: string
 */
PythonToBlock.prototype.Str = function (node) {
  const s = node.s
  return PythonToBlock.createBlock('text', node.lineno, {
    TEXT: Sk.ffi.remapToJs(s)
  })
}

/*
 * 声明字符串变量
 * s: list
 */
PythonToBlock.prototype.List = function (node) {
  const elts = node.elts
  return PythonToBlock.createBlock(
    'lists_create_with',
    node.lineno,
    {},
    this.convertElements('ADD', elts),
    {
      inline: 'false'
    },
    {
      '@items': elts.length
    }
  )
}

/**
 * 用于解析字符串用，转换将prompt语句转换为text
 */
PythonToBlock.prototype.strValue = function (node) {
  const s = node.s
  return Sk.ffi.remapToJs(s)
}

/*
 * 声明一些常量
 * id: identifier
 * ctx: expr_context_ty
 */
PythonToBlock.prototype.Name = function (node) {
  const id = node.id

  switch (this.nameStr(node)) {
    case 'True':
      return Python.createBlock('logic_boolean', node.lineno, {
        BOOL: 'TRUE'
      })
    case 'False':
      return PythonToBlock.createBlock('logic_boolean', node.lineno, {
        BOOL: 'FALSE'
      })
    case 'Not':
      return PythonToBlock.createBlock('logic_negate', node.lineno, {})
    case 'None':
      return null
    default:
      return PythonToBlock.createBlock('variables_get', node.lineno, {
        VAR: this.identifier(id)
      })
  }
}

/**
 * while 函数
 * @param {*} node
 * @param {*} parent
 * @returns
 */
PythonToBlock.prototype.While = function (node, parent) {
  const test = node.test
  const body = node.body
  const orelse = node.orelse
  const values = { BOOL: this.convert(test, node) }
  const statements = { DO: this.convertBody(body, node) }

  let hasOrelse = false
  if (orelse !== null && orelse.length > 0) {
    statements.ORELSEBODY = this.convertBody(orelse, node)
    hasOrelse = true
  }

  return PythonToBlock.createBlock(
    'controls_whileUntil',
    node.lineno,
    {
      MODE: 'WHILE'
    },
    values,
    {},
    {
      '@orelse': hasOrelse
    },
    statements
  )
}

/**
 * 解析部分常量
 * @param {*} node
 * @returns
 */
PythonToBlock.prototype.NameConstant = function (node) {
  const value = node.value
  switch (this.identifier(value)) {
    case true:
      return PythonToBlock.createBlock('logic_boolean', node.lineno, {
        BOOL: 'TRUE'
      })
    case false:
      return PythonToBlock.createBlock('logic_boolean', node.lineno, {
        BOOL: 'FALSE'
      })
    case null:
      return PythonToBlock.createBlock('logic_null', node.lineno)
    default:
      return PythonToBlock.createBlock('variables_get', node.lineno, {
        VAR: this.identifier(value)
      })
  }
}

/*
 * 解析常量用（如false和true）
 * id: identifier
 * ctx: expr_context_ty
 */
PythonToBlock.prototype.nameStr = function (node) {
  const id = node.id
  return this.identifier(id)
}

/* 上面是声明变量或者常量用的函数 */
/* 下面是声明表达式用的函数 */
PythonToBlock.prototype.Expr = function (node, parent) {
  const value = node.value
  const converted = this.convert(value, node)
  if (converted.constructor === Array) {
    return converted[0]
  } else {
    return this.convert(value, node)
  }
}

/**
 * 判断逻辑运算符
 * @param {*} op
 * @returns
 */
PythonToBlock.prototype.compareOperator = function (op) {
  const name = Sk.astDump(op)
  console.log(name)
  switch (name) {
    case 'Eq()':
      return 'EQ'
    case 'NotEq()':
      return 'NEQ'
    case 'Lt()':
      return 'LT'
    case 'Gt()':
      return 'GT'
    case 'LtE()':
      return 'LTE'
    case 'GtE()':
      return 'GTE'
    case 'In()':
      return 'IN'
    case 'NotIn()':
      return 'NOTIN'
    case 'Is()':
      return 'IS'
    case 'IsNot()':
      return 'ISNOT'
    // Is, IsNot
    default:
      throw new Error('Operator not supported:' + name)
  }
}

/**
 * 逻辑比较运算（大于、小于、等于、不等于等）
 * @param {*} node
 * @returns
 */
PythonToBlock.prototype.Compare = function (node) {
  const left = node.left
  const ops = node.ops
  const comparators = node.comparators
  if (ops.length !== 1) {
    throw new Error('Only one comparison operator is supported')
  } else if (
    Sk.astDump(ops[0]) === 'In()' ||
    Sk.astDump(ops[0]) === 'NotIn()' ||
    Sk.astDump(ops[0]) === 'Is()' ||
    Sk.astDump(ops[0]) === 'IsNot()'
  ) {
    return PythonToBlock.createBlock(
      'in_function',
      node.lineno,
      {
        in_op: this.compareOperator(ops[0])
      },
      {
        key: this.convert(left, node),
        dict: this.convert(comparators[0], node)
      },
      {
        inline: 'true'
      }
    )
  } else {
    return PythonToBlock.createBlock(
      'logic_compare',
      node.lineno,
      {
        OP: this.compareOperator(ops[0])
      },
      {
        A: this.convert(left),
        B: this.convert(comparators[0])
      },
      {
        inline: 'true'
      }
    )
  }
}

/**
 *异常的抛出函数
 * @param {*} node
 * @param {*} parent
 * @returns
 */
PythonToBlock.prototype.ExceptHandler = function (node, parent) {
  const body = node.body
  const type = node.type
  return PythonToBlock.createBlock(
    'controls_forEach',
    node.lineno,
    {
      VAR: PythonToBlock.prototype.nameStr(type.VAR) // targets
    },
    {},
    {},
    {},
    { DO: this.convertBody(body, node) }
  )
}

/**
 * args处理
 * @param {*} node
 * @param {*} parent
 * @returns
 */
PythonToBlock.prototype.arguments = function (node, parent) {
  const args = node.args
  return [this.convertElements('ADD', args, node), args.length]
}

/**
 * arg处理
 * @param {*} node
 * @param {*} parent
 * @returns
 */
PythonToBlock.prototype.arg = function (node, parent) {
  const arg = node.arg
  return PythonToBlock.createBlock('arg_keyword', node.lineno, {
    ARG: Sk.ffi.remapToJs(arg)
  })
}

/**
 * lambda功能
 * @param {*} node
 * @param {*} parent
 * @returns
 */
PythonToBlock.prototype.Lambda = function (node, parent) {
  const args = node.args
  const body = node.body
  const output = this.convert(args, node)
  output[0].LAMBDASTATE = this.convert(body, parent)
  return PythonToBlock.createBlock(
    'function_lambda',
    node.lineno,
    {},
    output[0],
    {},
    {
      '@items': output[1]
    }
  )
}

PythonToBlock.prototype.defBlocks = []
PythonToBlock.prototype.classBlocks = []

/**
 * define功能实现
 * @param {*} node
 * @param {*} parent
 * @returns
 */
PythonToBlock.prototype.FunctionDef = function (node, parent) {
  const name = node.name
  const args = node.args
  const body = node.body
  const output = this.convert(args, node)
  const funcDef = {}
  funcDef.name = Sk.ffi.remapToJs(name)
  funcDef.argLength = args.args.length
  let judge = true
  for (let i = 0; i < this.defBlocks.length; i++) {
    if (JSON.stringify(funcDef) === JSON.stringify(this.defBlocks[i])) {
      judge = false
    }
  }
  if (judge === true) {
    this.defBlocks.push(funcDef)
  }
  return PythonToBlock.createBlock(
    'function_def',
    node.lineno,
    {
      FUNCNAME: Sk.ffi.remapToJs(name)
    },
    output[0],
    {},
    {
      '@items': output[1]
    },
    {
      DEFSTATE: this.convertBody(body, node)
    }
  )
}

PythonToBlock.prototype.ast_Comment = function (txt, lineno) {
  const commentText = txt.slice(1)
  return PythonToBlock.createBlock('ast_Comment', lineno, {
    BODY: commentText
  })
}

PythonToBlock.prototype.Dict = function (node, parent) {
  const keys = node.keys
  const values = node.values

  if (keys === null) {
    return PythonToBlock.createBlock(
      'ast_Dict',
      node.lineno,
      {},
      {},
      { inline: 'false' },
      { '@items': 0 }
    )
  }

  const elements = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const value = values[i]
    elements['ADD' + i] = PythonToBlock.createBlock(
      'ast_DictItem',
      node.lineno,
      {},
      {
        KEY: this.convert(key, node),
        VALUE: this.convert(value, node)
      },
      this.LOCKED_BLOCK
    )
  }

  return PythonToBlock.createBlock(
    'ast_Dict',
    node.lineno,
    {},
    elements,
    {
      inline: 'false'
    },
    {
      '@items': keys.length
    }
  )
}

PythonToBlock.prototype.Assert = function (node, parent) {
  return PythonToBlock.createBlock(
    'function_assert',
    node.lineno,
    {},
    {
      statement: this.convert(node.test, node)
    }
  )
}

PythonToBlock.prototype.OPERATORS = {
  'Add()': '+',
  'Sub()': '-',
  'Mult()': '*',
  'Div()': '/',
  'Pow()': '**',
  'FloorDiv()': '//',
  'BitOr()': '|',
  'BitAnd()': '&',
  'Mod()': '%',
  'BitXor()': '^',
  'RShift()': '>>',
  'LShift()': '<<'
}

PythonToBlock.prototype.BinOp = function (node, parent) {
  const left = node.left
  const right = node.right
  return PythonToBlock.createBlock(
    'math_operators',
    node.lineno,
    {
      Operators: Sk.astDump(node.op)
    },
    {
      leftValue: this.convert(left, node),
      rightValue: this.convert(right, node)
    },
    {
      inline: true
    }
  )
}

PythonToBlock.prototype.Try = function (node, parent) {
  const body = node.body
  const orelse = node.orelse
  const handler = node.handlers
  const blockName = 'try_judgement'
  return PythonToBlock.createBlock(
    blockName,
    node.lineno,
    { exceptions: Sk.ffi.remapToJs(handler[0].type.id) }, // field
    {},
    {},
    {},
    {
      try: this.convertBody(body, node),
      except: this.convertBody(handler[0].body, node),
      else: this.convertBody(orelse, node)
    }
  )
}

PythonToBlock.prototype.Yield = function (node, parent) {
  return PythonToBlock.createBlock(
    'yield_function',
    node.lineno,
    {},
    {
      yieldStatement: this.convert(node.value, node)
    },
    {},
    {},
    {}
  )
}

PythonToBlock.prototype.Pass = function (node, parent) {
  return PythonToBlock.createBlock('logic_pass_', node.lineno)
}

PythonToBlock.prototype.Break = function (node, parent) {
  return PythonToBlock.createBlock('break', node.lineno)
}

PythonToBlock.prototype.Continue = function (node, parent) {
  return PythonToBlock.createBlock('continue', node.lineno)
}

PythonToBlock.prototype.Raise = function (node, parent) {
  const exc = node.exc
  const cause = node.cause
  const values = {}
  let hasExc = false
  let hasCause = false
  if (exc !== null) {
    values.EXC = this.convert(exc, node)
    hasExc = true
  }
  if (cause !== null) {
    values.CAUSE = this.convert(cause, node)
    hasCause = true
  }
  return PythonToBlock.createBlock(
    'ast_Raise',
    node.lineno,
    {},
    values,
    {},
    {
      '@exc': hasExc,
      '@cause': hasCause
    }
  )
}

PythonToBlock.prototype.For = function (node, parent) {
  const target = node.target
  const iter = node.iter
  const body = node.body
  const blockName = 'controls_forEach'
  const bodies = { DO: this.convertBody(body, node) }
  return PythonToBlock.createBlock(
    blockName,
    node.lineno,
    { VAR: Sk.ffi.remapToJs(target.id) }, // field
    {
      LIST: this.convert(iter, node)
    },
    {},
    {},
    bodies
  )
}

PythonToBlock.prototype.If = function (node, parent) {
  const body = node.body
  const compare = node.test
  const bodies = { DO0: this.convertBody(body, node) }
  if (Sk.astDump(node.orelse) !== '[]') {
    const blockName = 'controls_ifelse'
    const orelse = node.orelse
    return PythonToBlock.createBlock(
      blockName,
      node.lineno,
      {}, // field
      {
        IF0: this.convert(compare, node)
      },
      {},
      {},
      {
        DO0: this.convertBody(body, node),
        ELSE: this.convertBody(orelse, node)
      }
    )
  } else {
    const blockName = 'controls_if'
    return PythonToBlock.createBlock(
      blockName,
      node.lineno,
      {}, // field
      {
        IF0: this.convert(compare, node)
      },
      {},
      {},
      bodies
    )
  }
}

PythonToBlock.prototype.With = function (node, parent) {
  return PythonToBlock.createBlock('with_keyword', node.lineno)
}

PythonToBlock.prototype.Call = function (node, parent) {
  const args = node.args
  if (node.func._astname === 'Attribute') {
    return this.Attribute(node.func, node)
  }
  const funcName = Sk.ffi.remapToJs(node.func.id)
  const zeroBlock = PythonToBlock.createBlock('math_number', 1, { NUM: 0 })
  // 有两种情况，有参数和无参数时块的形状不同
  if (args !== null) {
    switch (funcName) {
      // 调用int()方法
      case 'int':
        return PythonToBlock.createBlock(
          'parse_int',
          node.lineno,
          {},
          {
            parsingState: this.convert(args[0], node)
          }
        )
      case 'print':
        return PythonToBlock.createBlock(
          'print_function',
          node.lineno,
          {},
          {
            SETENCE: this.convert(args[0], node)
          }
        )
      case 'float':
        return PythonToBlock.createBlock(
          'math_float',
          node.lineno,
          {},
          {
            NAME: this.convert(args[0], node)
          }
        )
      case 'floor':
        return PythonToBlock.createBlock(
          'math_round',
          node.lineno,
          {
            OP: 'ROUNDDOWN'
          },
          {
            NUM: this.convert(args[0], node)
          }
        )
      case 'round':
        return PythonToBlock.createBlock(
          'math_round',
          node.lineno,
          {},
          {
            NUM: this.convert(args[0], node)
          }
        )
      case 'str':
        return PythonToBlock.createBlock(
          'function_str',
          node.lineno,
          {},
          {
            int: this.convert(args[0], node)
          }
        )
      // 调用input()方法
      case 'input':
        return PythonToBlock.createBlock(
          'input_function',
          node.lineno,
          {},
          {
            VALUE: this.convert(args[0], node)
          }
        )

      case 'bool':
        return PythonToBlock.createBlock(
          'function_bool',
          node.lineno,
          {},
          {
            BOOL: this.convert(args[0], node)
          }
        )
      case 'abs':
        return PythonToBlock.createBlock(
          'abs_function',
          node.lineno,
          {},
          {
            parsing_state: this.convert(args[0], node)
          }
        )
      // 调用range方法
      case 'range':
        if (args.length === 1) {
          return PythonToBlock.createBlock(
            'range_function',
            node.lineno,
            {},
            {
              upperRange: zeroBlock,
              lowerRange: this.convert(args[0], node)
            }
          )
        } else {
          if (args.length === 2) {
            return PythonToBlock.createBlock(
              'range_function',
              node.lineno,
              {},
              {
                upperRange: this.convert(args[0], node),
                lowerRange: this.convert(args[1], node)
              }
            )
          } else {
            return PythonToBlock.createBlock('range_function', node.lineno)
          }
        }
      case 'pow':
        return PythonToBlock.createBlock(
          'function_pow',
          node.lineno,
          {},
          {
            A: this.convert(args[0], node),
            B: this.convert(args[1], node)
          }
        )
      case 'sum':
        if (args.length === 1) {
          return PythonToBlock.createBlock(
            'single_sum_function',
            node.lineno,
            {},
            {
              singleParameter: this.convert(args[0], node)
            }
          )
        } else if (args.length === 2) {
          return PythonToBlock.createBlock(
            'two_sum_function',
            node.lineno,
            {},
            {
              firstParameter: this.convert(args[0], node),
              secondParameter: this.convert(args[1], node)
            }
          )
        }
      // eslint-disable-next-line no-fallthrough
      case 'max':
      case 'min':
      case 'average':
        return PythonToBlock.createBlock(
          'math_on_list',
          node.lineno,
          { field_dropdown: Sk.ffi.remapToJs(node.func.id) },
          {
            LIST: this.convert(args[0], node)
          }
        )
      case 'bin':
        return PythonToBlock.createBlock(
          'function_bin',
          node.lineno,
          {},
          {
            int: this.convert(args[0])
          }
        )
      case 'walrus':
        return PythonToBlock.createBlock(
          'math_walrusexpression',
          node.lineno,
          {},
          {
            left: this.convert(args[0], node),
            right: this.convert(args[1], node)
          }
        )
      case 'reverse':
        return PythonToBlock.createBlock(
          'function_reverse',
          node.lineno,
          {},
          {
            list: this.convert(args[0])
          }
        )
      case 'ascii':
        return PythonToBlock.createBlock(
          'function_ascii',
          node.lineno,
          {},
          {
            parsing_state: this.convert(args[0], node)
          }
        )
      default:
        return PythonToBlock.createBlock('ast_Raw', node.lineno, {
          wrongCode: funcName
        })
    }
  } else {
    switch (funcName) {
      case 'int':
        return PythonToBlock.createBlock('parse_int', node.lineno)
      case 'float':
        return PythonToBlock.createBlock('math_float', node.lineno)
      case 'round':
        return PythonToBlock.createBlock('math_round', node.lineno)
      case 'print':
        return PythonToBlock.createBlock('print_function', node.lineno)
      case 'return':
        return PythonToBlock.createBlock('return', node.lineno)
      case 'floor':
        return PythonToBlock.createBlock(
          'math_round',
          node.lineno,
          {
            OP: 'ROUNDDOWN'
          },
          {}
        )
      case 'str':
        return PythonToBlock.createBlock('function_str', node.lineno)
      case 'input':
        return PythonToBlock.createBlock('input_function', node.lineno)
      case 'abs':
        return PythonToBlock.createBlock('abs_function', node.lineno)
      case 'range':
        return PythonToBlock.createBlock('range_function', node.lineno)
      case 'pow':
        return PythonToBlock.createBlock('function_pow', node.lineno)
      case 'bool':
        return PythonToBlock.createBlock('function_bool', node.lineno)
      case 'sum':
        return PythonToBlock.createBlock('single_sum_function', node.lineno)
      case 'max':
      case 'min':
      case 'average':
        return PythonToBlock.createBlock('math_on_list', node.lineno)
      case 'bin':
        return PythonToBlock.createBlock('function_bin', node.lineno)
      case 'walrus':
        return PythonToBlock.createBlock('math_walrusexpression', node.lineno)
      case 'reverse':
        return PythonToBlock.createBlock('function_reverse', node.lineno)
      case 'ascii':
        return PythonToBlock.createBlock('function_ascii', node.lineno)
      default:
        return PythonToBlock.createBlock('ast_Raw', node.lineno, {
          wrongCode: funcName
        })
    }
  }
}

PythonToBlock.prototype.Return = function (node, parent) {
  const value = node.value
  if (value === null) {
    return PythonToBlock.createBlock('returnnull', node.lineno)
  } else {
    return PythonToBlock.createBlock(
      'return',
      node.lineno,
      {},
      {
        SETENCE: this.convert(value, node)
      }
    )
  }
}

/**
 * Construct the blocks required by the flyout for the colours category.
 * @param {!Blockly.Workspace} workspace The workspace this flyout is for.
 * @return {!Array.<!Element>} Array of XML block elements.
 */
PythonToBlock.prototype.defineFlyoutCallback = function (workspace) {
  const defList = PythonToBlock.prototype.defBlocks
  const xmlList = []
  xmlList.push(Blockly.Xml.textToDom('<block type="function_def"></block>'))
  for (let i = 0; i < defList.length; i++) {
    let blockText =
      '<block type="callback_def_with_return"><mutation items="' +
      defList[i].argLength +
      '"></mutation>' +
      '<field name="FUNCNAME">' +
      defList[i].name +
      '</field>'
    for (let j = 0; j < defList[i].argLength; j++) {
      blockText = blockText + '<value name="ADD' + j + '"></value>'
    }
    blockText = blockText + '</block>'
    const block = Blockly.Xml.textToDom(blockText)
    xmlList.push(block)
  }
  for (let i = 0; i < defList.length; i++) {
    let blockText =
      '<block type="callback_def_without_return"><mutation items="' +
      defList[i].argLength +
      '"></mutation>' +
      '<field name="FUNCNAME">' +
      defList[i].name +
      '</field>'
    for (let j = 0; j < defList[i].argLength; j++) {
      blockText = blockText + '<value name="ADD' + j + '"></value>'
    }
    blockText = blockText + '</block>'
    const block = Blockly.Xml.textToDom(blockText)
    xmlList.push(block)
  }
  return xmlList
}

PythonToBlock.prototype.ClassDef = function (node, parent) {
  const name = node.name
  const body = node.body
  return PythonToBlock.createBlock(
    'class_procedure',
    node.lineno,
    {
      CLASSNAME: Sk.ffi.remapToJs(name)
    },
    {},
    {},
    {},
    {
      METHODS: this.convertBody(body, node)
    }
  )
}

PythonToBlock.prototype.Import = function (node, parent) {
  console.log(Sk.astDump(node))
  const names = node.names
  const output = this.convertElements('ADD', names, node)
  return PythonToBlock.createBlock(
    'function_import',
    node.lineno,
    {},
    output,
    {},
    {
      '@items': names.length
    }
  )
}

PythonToBlock.prototype.alias = function (node, parent) {
  const name = node.name
  const asname = node.asname
  if (asname !== null) {
    return PythonToBlock.createBlock(
      'as_key',
      1,
      {},
      {
        leftValue: PythonToBlock.createBlock('variables_get', 1, {
          VAR: Sk.ffi.remapToJs(name)
        }),
        rightValue: PythonToBlock.createBlock('variables_get', 1, {
          VAR: Sk.ffi.remapToJs(asname)
        })
      }
    )
  } else {
    return PythonToBlock.createBlock('variables_get', 1, {
      VAR: Sk.ffi.remapToJs(name)
    })
  }
}

PythonToBlock.prototype.ImportFrom = function (node, parent) {
  const moduleFrom = node.module
  const names = node.names
  const output = this.convertElements('ADD', names, node)
  output.from = PythonToBlock.createBlock('variables_get', 1, {
    VAR: Sk.ffi.remapToJs(moduleFrom)
  })
  return PythonToBlock.createBlock(
    'function_import_from',
    node.lineno,
    {},
    output,
    {},
    {
      '@items': names.length
    }
  )
}

PythonToBlock.prototype.Attribute = function (node, parent) {
  const value = node.value
  const attr = node.attr
  if (parent._astname === 'Call') {
    const args = parent.args
    const output = this.convertElements('ADD', args, node)
    return PythonToBlock.createBlock(
      'function_attr',
      node.lineno,
      {
        lib_name: Sk.ffi.remapToJs(node.value.id),
        func_name: Sk.ffi.remapToJs(node.attr)
      },
      output,
      {},
      {
        '@items': args.length
      }
    )
  }
  if (value._astname === 'Name') {
    return PythonToBlock.createBlock('ast_Attribute', node.lineno, {
      VALUE: Sk.ffi.remapToJs(value.id),
      ATTR: Sk.ffi.remapToJs(attr)
    })
  } else {
    return PythonToBlock.createBlock(
      'ast_AttributeFull',
      node.lineno,
      {
        ATTR: Sk.ffi.remapToJs(attr)
      },
      {
        VALUE: this.convert(value, node)
      }
    )
  }
}

export default PythonToBlock
